package gao.xiaolei.blue;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;

import java.io.Serializable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

import gao.xiaolei.blue.callback.ConnectCallback;
import gao.xiaolei.blue.callback.OnReceiverCallback;
import gao.xiaolei.blue.callback.OnWriteCallback;
import gao.xiaolei.blue.callback.ScanCallback;
import gao.xiaolei.blue.util.HexUtil;

/**
 * 蓝牙模块的封装类，负责蓝牙初始化、蓝牙连接以及消息的发送和接收数据，申请权限和开启蓝牙功能则由activity来负责
 */
public class BlueToothController implements Serializable {
    private Context mContext;//当前应用的上下文
    private boolean isConnecting=false;//判断当前状态是否已经连接
    private boolean isScanning=false;//判断当前是否在扫描
    private boolean isScannedTarget=false;//判断当前是否已经扫描到目标设备了
    private boolean isInitiativeToDisconnect=false;//判断此次断开连接是主动断开还是被动断开
   // private boolean isRegister=false;//判断是否已经注册receiver了
    private BluetoothManager mBluetoothManager;//本地蓝牙管理
    private BluetoothAdapter mBluetoothAdapter;//本地蓝牙适配器
    //private BleReceiver mBleReceiver;//蓝牙信息接收器
    private BluetoothGatt mBleGatt;//当前连接的蓝牙设备的Gatt连接
    private HashMap<String, Map<String, BluetoothGattCharacteristic>> servicesMap = new HashMap<>();//储存蓝牙设备所有的服务UUID和对应的特征值
    private Handler mHandler = new Handler(Looper.getMainLooper());//为了定时停止扫描
    private static final int SCAN_TIME = 10000;//默认扫描时间：10s

    private String BLUETOOTH_NOTIFY_D;//此属性是用来开启通知的时获取notify特征值的描述对象，因为开启通知要通过描述对象，但这个值貌似没有什么规定，只要是个uuid就可以了，不知道为什么
    private Set<String> alReadyExistsNotify=new HashSet<>();//用来储存已经开启了的notify特征值，防止多次开启
    //private String driverName;//当前连接的蓝牙名称
    private String deviceName;//储存在map中到达key
    private int uuidDataType;//要连接的蓝牙设备的广播uuid类型标示符
    private String driverUuid;//要连接的蓝牙设备的广播uuid
    private String notifyUuid;//要开启notify通道的Uuid,因为不知道为什么如果等到发送消息的时候才去开启notify会导致第一次发送消息不会生效,所以打算在服务发现时就将他开启


    private ConnectCallback mConnectBack;//连接时调用的回调
    private OnWriteCallback mWriterCallback;//写入数据之后的回调
    private OnReceiverCallback mReceiverCallback;//接收notify数据之后的回调
    private ScanCallback mScanCallback;//扫描设备的回调

    public static BlueToothController getInstance(int uuidDataType, String driverUuid, String notify_d, String notifyUuid, Context context){//静态工厂来获取实例
        return new BlueToothController(uuidDataType,driverUuid,
                notify_d==null?"00002902-0000-1000-8000-00805f9b34fb":notify_d,
                notifyUuid,
                context);
    }

    public String getDeviceName() {
        return deviceName;
    }

    public void setDeviceName(String deviceName) {
        this.deviceName = deviceName;
    }

    public void setScanCallback(ScanCallback scanCallback){//设置扫描回调
        this.mScanCallback=scanCallback;
    }

    public void setConnectCallback(ConnectCallback connectCallback){//设置连接回调
        this.mConnectBack=connectCallback;
    }

    public void setWriterCallback(OnWriteCallback writerCallback){//设置写入数据回调
        this.mWriterCallback=writerCallback;
    }

    public void setReceiverCallback(OnReceiverCallback receiverCallback){//设置接收notify数据回调
        this.mReceiverCallback=receiverCallback;
    }

    private BlueToothController(int uuidDataType, String driverUuid, String notify_d, String notifyUuid, Context context){//将构造函数私有化
        this.uuidDataType=uuidDataType;
        this.driverUuid=driverUuid;
        this.notifyUuid=notifyUuid;
        this.BLUETOOTH_NOTIFY_D=notify_d;
        this.mContext=context;
    }

    /**
     * 初始化蓝牙
     */
    public void initBle() {
        mBluetoothManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);//调用系统服务获取蓝牙管理对象
        if (mBluetoothManager == null) {
            Toast.makeText(mContext, "蓝牙不可用", Toast.LENGTH_LONG).show();
            return;
        }
        mBluetoothAdapter = mBluetoothManager.getAdapter();//获取本地蓝牙适配器
        if (!mBluetoothAdapter.isEnabled()) {//如果蓝牙没有开启则向系统请求开启蓝牙
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            mContext.startActivity(intent);//此方法用来打开一个新的activity来让让用户确认是否要打开蓝牙
        }
    }

    /**
     * 连接设备
     */
    public void connectDriver(){
        if(!isConnecting){//如果已经连接了就不用再连接了
            if(!ifInitBle())
                initBle();//初始化蓝牙
//            if(!isRegister)
//                registerBleReceiver();//注册receiver
            scanBleDevice(10000);//扫描设备
        }
    }

    /**
     * 手动断开设备
     */
    public void disConnectDriver(){
        if(isConnecting){//如果已经断开连接了就不用再断开了
            isInitiativeToDisconnect=true;//标志此次是手动断开连接的
            mBleGatt.disconnect();//断开连接
            isConnecting=false;//将状态修改为断开连接
            isScannedTarget=false;//标志没有找到目标设备
            isScanning=false;//防止还有定时停止扫描的任务在执行
            servicesMap.clear();//清空设备的服务集合
            alReadyExistsNotify.clear();//清空开启了通知的notify集合
            if(mConnectBack!=null)
                mConnectBack.onConnFailed(this);//连接断开后的调用
        }
    }

    /**
     * 判断该设备是否已经连接
     */
    public boolean isConnecting(){
        return isConnecting;
    }

    /**
     * 判断是否扫描到了目标设备
     */
    public boolean isScannedTarget(){
        return isScannedTarget;
    }

    /**
     * 判断本次连接是否是手动断开的连接
     */
    public boolean isInitiativeToDisconnect(){
        return isInitiativeToDisconnect;
    }

    /**
     * 根据特征值发送数据到蓝牙设备
     */
    public void sendData(String serviceUuid, String writeUuid, String notifyUuid, byte[] data, boolean isWithResult){
        connectDriver();//看看设备是否有连接
        if(enableNotification(isWithResult,getBluetoothGattCharacteristic(serviceUuid,notifyUuid)))//开启通知
            writeData(serviceUuid,writeUuid,data);//发送数据
    }


    /**
     * 判断蓝牙是否初始化
     */
    private boolean ifInitBle(){
        return mBluetoothManager!=null&&mBluetoothAdapter!=null;
    }

    /**
     * 搜索蓝牙设备
     * time：最长扫描多久
     * startDiscovery()方法在大多数手机上是可以同时发现经典蓝牙和低功耗蓝牙（BLE）的，
     * 但是startDiscovery()的回调无法返回BLE的广播，所以无法通过广播识别设备，
     * 而且startDiscovery()扫描BLE效率比startLeScan()低很多。
     * 因此需要根据具体的需求去做适配，才能更高效的搜寻蓝牙。
     * startleScan和stopLeScan的callback对象要一样，不然无法停止扫描
     * PS: startLeScan()和startScan()有重载方法可以指定规则，参数去搜索,但是在api21以上被标记废弃使用。
     */
    private void scanBleDevice(int time) {
        //如果当前在搜索，就先取消搜索
//        android.bluetooth.le.ScanCallback mCall=new android.bluetooth.le.ScanCallback() {
//            @Override
//            public void onScanResult(int callbackType, ScanResult result) {
//                super.onScanResult(callbackType, result);
//                System.out.println("开始扫描");
//                System.out.println("callbackType:"+callbackType);
//            }
//        };
//        mBluetoothAdapter.getBluetoothLeScanner().startScan(mCall);
//        mHandler.postDelayed(new Runnable() {//time后停止扫描
//            @Override
//            public void run() {
//                System.out.println("定时停止。。。。。。。。。。。。。。。");
//                mBluetoothAdapter.getBluetoothLeScanner().stopScan(mCall);
//            }
//        }, time <= 0 ? SCAN_TIME : time);
//        mBluetoothAdapter.getBluetoothLeScanner().stopScan(new android.bluetooth.le.ScanCallback() {
//            @Override
//            public void onScanResult(int callbackType, ScanResult result) {
//                super.onScanResult(callbackType, result);
//                System.out.println("结束扫描");
//                System.out.println("callbackType:"+callbackType);
//            }
//        });
//        mBluetoothAdapter.stopLeScan(new BluetoothAdapter.LeScanCallback() {
//            @Override
//            public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
//                System.out.println("停止扫描");
//            }
//        });
//        if (mBluetoothAdapter.isDiscovering()) {
//            mBluetoothAdapter.cancelDiscovery();
//        }
        //mBluetoothAdapter.startDiscovery();
        //如果当前在搜索，就先取消搜索
        isScanning=false;
        mBluetoothAdapter.stopLeScan(mLeScanCallback);
        //开启搜索
        isScanning=true;
        isScannedTarget=false;
        isInitiativeToDisconnect=false;//先将是否手动断开连接的置为原位
        mBluetoothAdapter.startLeScan(mLeScanCallback);
        mHandler.postDelayed(()-> {//time后停止扫描
            if(isScanning){//如果还在扫描才去执行定时停止
                System.out.println("定时停止。。。。。。。。。。。。。。。");
                isScanning=false;
                mBluetoothAdapter.stopLeScan(mLeScanCallback);
                if(mScanCallback!=null)//不知道为什么debug的时候停止了会调用mLeScanCallback的方法，但是运行的时候就不会调用，所以只能把代码放这里了
                    mScanCallback.onSuccess(BlueToothController.this);//搜索完成的回调
            }
        }, time <= 0 ? SCAN_TIME : time);
    }

    /**
     * 连接蓝牙设备，通过目标设备的蓝牙地址建立Gatt连接
     */
    private void connectDevice(String address){
        if(!isConnecting){//如果当前没有连接才去连接
            BluetoothDevice targetDevice=mBluetoothAdapter.getRemoteDevice(address);
            mBleGatt = targetDevice.connectGatt(mContext, false,new BleGattCallback());
        }
    }

    /**
     * 设置通知
     *
     * @param enable         true为开启false为关闭
     * @param characteristic 通知特征
     * @return
     */
    private boolean enableNotification(boolean enable, BluetoothGattCharacteristic characteristic) {
        if (mBleGatt == null || characteristic == null)
            return false;
        if(enable&&alReadyExistsNotify.contains(characteristic.getUuid().toString()))//如这个特征值已经开启了通知则不用再次开启了
            return true;
        if (!mBleGatt.setCharacteristicNotification(characteristic, enable))//开启这个notify特征值的通知功能，其实执行这一步之后就可以获得设备传来的数据了，这步不成功，也不会发送数据，不知道下面几步的意义是什么
            return false;
        BluetoothGattDescriptor clientConfig = characteristic.getDescriptor(UUID.fromString(BLUETOOTH_NOTIFY_D));
        if (clientConfig == null)
            return false;
        if (enable) {
            alReadyExistsNotify.add(characteristic.getUuid().toString());//开启通知则添加进去
            clientConfig.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
        } else {
            alReadyExistsNotify.remove(characteristic.getUuid().toString());//取消通知则移除
            clientConfig.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
        }
        return mBleGatt.writeDescriptor(clientConfig);
    }
    /**
     * 发送数据
     *
     */
    private void writeData(String serviceUuid, String writeUuid, byte[] data) {
        BluetoothGattCharacteristic mBleGattCharacteristic = getBluetoothGattCharacteristic(serviceUuid,writeUuid);
        //设置数组进去
        mBleGattCharacteristic.setValue(data);
        //发送
       mBleGatt.writeCharacteristic(mBleGattCharacteristic);

    }

    /**
     * 根据服务UUID和特征UUID,获取一个特征{@link BluetoothGattCharacteristic}
     *
     * @param serviceUUID   服务UUID
     * @param characterUUID 特征UUID
     */
    private BluetoothGattCharacteristic getBluetoothGattCharacteristic(String serviceUUID, String characterUUID) {

        //找服务
        Map<String, BluetoothGattCharacteristic> bluetoothGattCharacteristicMap = servicesMap.get(serviceUUID);
        if (null == bluetoothGattCharacteristicMap) {
            return null;
        }
        //找特征
        Set<Map.Entry<String, BluetoothGattCharacteristic>> entries = bluetoothGattCharacteristicMap.entrySet();
        BluetoothGattCharacteristic gattCharacteristic = null;
        for (Map.Entry<String, BluetoothGattCharacteristic> entry : entries) {
            if (characterUUID.equals(entry.getKey())) {
                gattCharacteristic = entry.getValue();
                break;
            }
        }
        return gattCharacteristic;
    }

    /**
     * 蓝牙GATT连接及操作事件回调
     */
    private class BleGattCallback extends BluetoothGattCallback implements Serializable {
        //连接状态改变
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {

            if (newState == BluetoothProfile.STATE_CONNECTED) { //连接成功
                isConnecting=true;//将状态修改为已经连接
                mBleGatt.discoverServices();//去发现服务
                if(mConnectBack!=null)
                    mConnectBack.onConnSuccess(BlueToothController.this);//连接成功后的调用
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { //断开连接
                isScannedTarget=false;//标志没有扫描到目标设备
                isConnecting=false;//将状态修改为断开连接
                servicesMap.clear();//清空设备的服务集合
                alReadyExistsNotify.clear();//清空开启了通知的notify集合
                if(mConnectBack!=null)
                    mConnectBack.onConnFailed(BlueToothController.this);//连接失败后的调用
            }
        }
        //发现新服务
        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            super.onServicesDiscovered(gatt, status);
            if (null != mBleGatt && status == BluetoothGatt.GATT_SUCCESS) {
                List<BluetoothGattService> services = mBleGatt.getServices();
                for (int i = 0; i < services.size(); i++) {//将新服务和其特征值加入Map
                    HashMap<String, BluetoothGattCharacteristic> charMap = new HashMap<>();
                    BluetoothGattService bluetoothGattService = services.get(i);
                    String serviceUuid = bluetoothGattService.getUuid().toString();
                    System.out.println("发现新服务："+serviceUuid);
                    List<BluetoothGattCharacteristic> characteristics = bluetoothGattService.getCharacteristics();
                    for (int j = 0; j < characteristics.size(); j++) {
                        charMap.put(characteristics.get(j).getUuid().toString(), characteristics.get(j));
                        System.out.println("发现新特征值："+characteristics.get(j).getUuid().toString());
                        if(notifyUuid.equals(characteristics.get(j).getUuid().toString()))//在服务发现阶段就将notify开启
                            enableNotification(true,characteristics.get(j));
                    }
                    servicesMap.put(serviceUuid, charMap);
                }
            }
        }


        //写数据完成后调用的方法
        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicWrite(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {//如果写入数据成功了
                if(mWriterCallback!=null)
                    mWriterCallback.onSuccess(characteristic.getUuid().toString());
            } else {//如果失败了
                if(mWriterCallback!=null)
                    mWriterCallback.onFailed(characteristic.getUuid().toString(),status);
            }
        }

        //通知数据
        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicChanged(gatt, characteristic);
            byte[] data=characteristic.getValue();
            if(mReceiverCallback!=null)
                mReceiverCallback.onReceiver(BlueToothController.this,characteristic.getUuid().toString(),data);
        }
    }

    /**
     * 从蓝牙的广播包中提取出设备的UUID，从而判断是不是目标设备
     */
    private String getDriverUuidFromRawData(String rowData, int targetDateType){
        for(int i=0,length=rowData.length()-1;i<length;){
            int dataLength= Integer.parseInt(rowData.substring(i,i+2),16);//获取数据长度
            if(dataLength==0)
                return null;
            i=i+2;
            int dataType= Integer.parseInt(rowData.substring(i,i+2),16);
            i=i+2;
            if(dataType==targetDateType)//如果这个数据是设备的UUID号的话
                return rowData.substring(i,i+dataLength*2-2).toLowerCase();
            i=i+dataLength*2-2;
        }
        return null;
    }

    /**
     * 使用startLeScan()是要传入的回调函数
     */
    private BluetoothAdapter.LeScanCallback mLeScanCallback= new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
            System.out.println(device.getName()+"-------"+ HexUtil.bytesToHexString(scanRecord));
            if(isScanning){//如果还在扫描的话
                System.out.println(device.getName()+"-------"+ HexUtil.bytesToHexString(scanRecord));
                System.out.println(device.getAddress());
                if(driverUuid.toLowerCase().equals(getDriverUuidFromRawData(HexUtil.bytesToHexString(scanRecord),uuidDataType))){//如果是目标设备
                    isScannedTarget=true;//标志找到了目标设备
                    mBluetoothAdapter.stopLeScan(this);//先停止扫描
                    isScanning=false;//将扫描状态置为否
                    connectDevice(device.getAddress());//连接设备
                }
                if(mScanCallback!=null)
                    mScanCallback.onScanning(device);//扫描到一个设备的回调
            }else{
                if(mScanCallback!=null)
                    mScanCallback.onSuccess(BlueToothController.this);//搜索完成的回调
            }
        }
    };

/////////////////////////////////////////////使用startDiscovery()来扫描蓝牙设备才需要用到下面的代码
    //    /**
//     * 注册蓝牙信息接收器
//     */
//    private void registerBleReceiver() {
//        IntentFilter filter = new IntentFilter();//要想在receiver中收到某个事件的广播就一定要在filter中注册，不然会收不到
//        filter.addAction(BluetoothDevice.ACTION_FOUND);//每搜索到一个设备就会发送一个该广播
//        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//当全部搜索完后发送该广播
//        filter.setPriority(Integer.MAX_VALUE);//设置优先级
//        // 注册蓝牙搜索广播接收者，接收并处理搜索结果
//        mBleReceiver = new BleReceiver();
//        mContext.registerReceiver(mBleReceiver, filter);
//        isRegister=true;
//    }

//    /**
//     * 蓝牙信息接收器，负责处理连接到某个蓝牙设备前即搜索设备的信息接收
//     */
//    private class BleReceiver extends BroadcastReceiver {
//
//        @Override
//        public void onReceive(Context context, Intent intent) {
//
//            String action = intent.getAction();
//            if (TextUtils.isEmpty(action)) {
//                return;
//            }
//            switch (action) {
//                case BluetoothDevice.ACTION_FOUND://当搜索到一个蓝牙设备时
//                    BluetoothDevice bl=intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//                    if(driverName.equals(bl.getName())){//如果是目标设备
//                        mBluetoothAdapter.cancelDiscovery();//先停止搜索，免得浪费性能
//                        connectDevice(bl.getAddress());//连接设备
//                    }
//                    if(mScanCallback!=null)
//                        mScanCallback.onScanning(bl);//扫描到一个设备的回调
//                    break;
//                case BluetoothAdapter.ACTION_DISCOVERY_FINISHED://当蓝牙设备搜索完毕时
//                    if(mScanCallback!=null)
//                        mScanCallback.onSuccess();//搜索完成的回调
//                    break;
//                case BluetoothAdapter.ACTION_DISCOVERY_STARTED://当开始搜索设备时
//                    break;
//                default:
//                    break;
//            }
//        }
//    }
}
